#include "AudioPlayer.hpp"

static long long get_tick(void)
{
    long long rev;
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    rev = ts.tv_sec * 1000ll + ts.tv_nsec / 1000000;
    return rev;
}
static long long get_tick_us(void)
{
    long long rev;
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    rev = ts.tv_sec * 1000000ll + ts.tv_nsec / 1000;
    return rev;
}

// #define ALSA_CARD "pulse"
#define ALSA_CARD "bluealsa"
//#define ALSA_CARD "sysdefault"

AudioPlayer::AudioPlayer(FRunLoop *runLoop, const char *path)
{
    this->runLoop = runLoop;
    this->filePath = strdup(path);
}

const AudioPCMInfo *AudioPlayer::getPCMInfo()
{
    if (decoder == NULL) return NULL;
    return decoder->getPCMInfo();
}

AudioPlayer::State AudioPlayer::getState() {return state;}
const char *AudioPlayer::stateToString(State state)
{
    switch (state) {
        case State::IDLE:    return "IDLE";
        case State::LOADING: return "LOADING";
        case State::PLAYING: return "PLAYING";
        case State::PAUSED:  return "PAUSED";
    }
    return nullptr;

}


AudioPlayer::Error AudioPlayer::getErrorInfo(char **desc)
{
    return Error::DECODER_ERR;
}

bool AudioPlayer::alsaStart(const AudioPCMInfo *info)
{
    int rev;
    snd_pcm_hw_params_t *hwParms = nullptr;
    snd_pcm_t *pcm = nullptr;
    unsigned int simplerate = info->simplerate;
    snd_pcm_uframes_t size = 0;
    int dir;
	unsigned int ui;
    //snd_pcm_uframes_t frames = 1024;

    if (alsaPCM) return true;

    rev = snd_pcm_open(&pcm, ALSA_CARD,
                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
    if (rev <0)
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }
    rev = snd_pcm_hw_params_malloc(&hwParms);
    if(rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }
    
    /*按照默认设置对硬件对象进行设置，并判断是否设置成功*/
    rev = snd_pcm_hw_params_any(pcm, hwParms);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;

    }
    rev = snd_pcm_hw_params_set_access(pcm, hwParms, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (rev < 0)
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }

    /*设置数据编码格式，并判断是否设置成功*/
    rev = snd_pcm_hw_params_set_format(pcm, hwParms,SND_PCM_FORMAT_S16_LE);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }


    rev = snd_pcm_hw_params_set_rate_near(pcm, hwParms, &simplerate, 0);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }
    printf("near simpleate:%d\n", (int)simplerate);

    rev = snd_pcm_hw_params_set_channels(pcm, hwParms, info->channel);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }

#if 1
    size = info->simplerate/100;
    rev = snd_pcm_hw_params_set_period_size_near(pcm, hwParms, &size, &dir);
    if (rev < 0) 
    {
        printf("AudioPlayer set period_size err\n");
        goto err;
    }
    printf("period size:%d dir:%d\n", (int)size, (int)dir);
#endif

#if 1
	printf("### setbuffer \n");
	size = info->simplerate;
	rev = snd_pcm_hw_params_set_buffer_size_near(pcm, hwParms, &size);
    if (rev < 0) 
    {
        printf("AudioPlayer set set_buffer_size err\n");
        goto err;
    }
	printf("### setbuffer size:%d\n", (int)size);
#endif

#if 0
	ui = 1000;
	rev = snd_pcm_hw_params_set_buffer_time_near(pcm, hwParms, &ui, &dir);
    if (rev < 0) 
    {
        printf("AudioPlayer set_buffer_time err\n");
        goto err;
    }
	printf("### set_buffer_time size:%d\n", (int)ui);
#endif
    rev = snd_pcm_hw_params(pcm, hwParms);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }

    size = 0;
    snd_pcm_hw_params_get_buffer_size(hwParms, &size);
    printf("alsa hw_params buffer size:%d\n", (int)size);

    rev = snd_pcm_prepare(pcm);
    if (rev < 0) 
    {
        printf("AudioPlayer alsa start err\n");
        goto err;
    }
    snd_pcm_hw_params_free(hwParms);
    alsaPCM = pcm;
    return true;
err:
    if (hwParms) snd_pcm_hw_params_free(hwParms);
    if (pcm) snd_pcm_close(pcm);
    return false;
}

void AudioPlayer::alsaStop()
{
	if (alsaPCM == nullptr) return;
    snd_pcm_close(alsaPCM);
    alsaPCM = nullptr;
}

void AudioPlayer::alsaPause(bool enable)
{
    if (alsaPCM == nullptr) return;
    snd_pcm_pause(alsaPCM, enable);
}

void AudioPlayer::alsaPoll(bool enable)
{
    int rev, i;
    if (enable) {
        if (ufdsCount > 0) return;
        rev = snd_pcm_poll_descriptors_count(alsaPCM);
        F_ASSERT(rev > 0);
        printf("-------- enablePoll\n");

        ufdsCount = rev;
        ufds = (struct pollfd *)calloc(rev, sizeof(*ufds));
        ufdsPoll = (FPoll **)calloc(rev, sizeof(*ufdsPoll));

        rev = snd_pcm_poll_descriptors(alsaPCM, ufds, rev);
        printf("pcm_poll rev:%d cnt:%d\n", rev, ufdsCount);

        for (i = 0; i < ufdsCount; i++) {
            struct pollfd *fd = ufds + i;
            printf("%d:poll fd:%d event:%d revent:%d\n", i, fd->fd, fd->events, fd->revents);
            if (fd->events & POLLPRI) {
                printf("Poll prioty ??\n");
                abort();
            }
            uint32_t event = 0;
            if (fd->events & POLL_IN)  event |= F_EVENT_IN;
            if (fd->events & POLL_OUT) event |= F_EVENT_OUT;
            if (event == 0) {
                printf("Poll no input ?? for fd:%d\n", fd->fd);
                continue;
            }
            ufdsPoll[i] = runLoop->addPoll(fd->fd, event, [this, i, fd](FPoll *poll, uint32_t event) {
                if (event & F_EVENT_IN)  fd->revents |= POLLIN;
                if (event & F_EVENT_OUT) fd->revents |= POLLOUT;
                if (event & F_EVENT_ERR) fd->revents |= POLLERR;

                unsigned short revents = 0;
                snd_pcm_poll_descriptors_revents(alsaPCM, ufds, ufdsCount, &revents);
                if (revents & POLLIN) // 录音
                {
                    printf("## rec\n");
                }

                if (revents & POLLOUT) // 播放
                {
                    // printf("## play %p\n", decoder);
                    tryWritePCM();
                }
            });
        }
        // abort();
    } else {
        printf("-------- removePoll\n");
        if (ufds == nullptr) return;
        for (i = 0; i < ufdsCount; i++) {
            FPoll *p = ufdsPoll[i];
            if (p) delete p;
        }
        ufdsCount = 0;
        ufdsPoll = nullptr;
        ufds = nullptr;
    }
}

void AudioPlayer::tryWritePCM()
{
    int16_t *pcmi;
    int pcmLeni;
    long long t1 = get_tick_us();
    if (t1 - prevWriteTick < 50) {
        // printf("Poll wait %lld\n", t1 - prevWriteTick);
    }
    if (pcmOffset >= pcmLen) {
        Mp3Decoder::State decState = decoder->getState();
        pcmLen = decoder->readPCM(pcm, sizeof(pcm)/2);
        if (pcmCB) pcmCB(this, pcm, pcmLen);
        //printf("readed:%d\n", pcmLen*2);
        //decoder->printCache();
        pcmOffset = 0;
        pcmLeni = pcmLen;
        pcmi = pcm;

        if (pcmLen <= 0) {
            printf("read pcm empty\n");
            if (decState == Mp3Decoder::State::FINISH) {
                stop();
                if (stateChangeCB) stateChangeCB(this);
            } else {
                alsaPoll(false);
            }
            return;
        }
    } else {
        pcmi = pcm + pcmOffset;
        pcmLeni = pcmLen - pcmOffset;
    }
    const AudioPCMInfo *info = decoder->getPCMInfo();
    t1 = get_tick_us();
    int writei = snd_pcm_writei(alsaPCM, pcmi, pcmLeni/info->channel);
    long long t2 = get_tick_us();
    prevWriteTick = t2;
    if (t2 - t1 > 2000)
        printf("write:%d rev:%d used:%lld\n", pcmLeni, writei, (t2-t1)/1000);
    if (writei < 0) {
        printf("writei:%d info->ch:%d failed retry\n", writei, info->channel);
        return;
    }
    pcmOffset += writei * info->channel;
    if (pcmOffset < 0) {
        abort();
    }
}

void AudioPlayer::seekTo(int ms)
{
    if (state <= State::LOADING) {
        printf("AudioPlayer load but state:%d err\n", state);
        return;
    }
    pcmLen = 0;
    decoder->seekTo(ms);
}

void AudioPlayer::registDecoder(Mp3Decoder *decoder)
{
    decoder->stateChangeCB = [this](Mp3Decoder *decoder) {
        Mp3Decoder::State state = decoder->getState();
        if (state <= Mp3Decoder::State::LOADING) return;

        if (this->state == State::LOADING) {
            const AudioPCMInfo *info = decoder->getPCMInfo();
            printf("mp3 ch:%d simplerate:%d bitrate:%d\n", info->channel,
                   info->simplerate, info->bitrate);
            this->state = State::PAUSED;
            if (stateChangeCB) stateChangeCB(this);
        }

        if (State::PAUSED == this->state) {
            ;
        } else {
            alsaPoll(true);
        }
    };
}

bool AudioPlayer::load()
{
    bool ok;
    if (state != State::IDLE) {
        printf("AudioPlayer load but state:%d err\n", state);
        return false;
    }

    decoder = new Mp3Decoder(runLoop, filePath);
    registDecoder(decoder);

    ok = decoder->start();
    if (!ok) {
        decoder->destroy();
        decoder = nullptr;
        return false;
    }

    state = State::LOADING;
    return true;
}

bool AudioPlayer::resume()
{
    int rev;
    bool ok;
    if (state != State::PAUSED) {
        printf("AudioPlayer load but state:%d err\n", state);
        return false;
    }
    const AudioPCMInfo *info = decoder->getPCMInfo();
    ok = alsaStart(info);
    printf("Alsa opened:%s\n", ok ? "Ok": "Failed");
    if (!ok) {
        return false;
    }

    alsaPause(false);
    alsaPoll(true);
    state = State::PLAYING;

    return true;
}

void AudioPlayer::pause()
{
    bool ok;
    if (state != State::PLAYING) {
        printf("AudioPlayer load but state:%d err\n", state);
        return;
    }

    state = State::PAUSED;
    alsaPoll(false);
    alsaPause(true);
}

void AudioPlayer::stop()
{
    if (decoder) {
        decoder->destroy();
        decoder = nullptr;
    }
    alsaPoll(false);
    alsaStop();
    pcmLen = 0;
    state = State::IDLE;
}

AudioPlayer::~AudioPlayer()
{
    stop();
    free(filePath);
}

void AudioPlayer::destroy()
{
    F_ASSERT(!postedDestroy);
    FEvent *event = new FEvent([this](FEvent *e) {
        delete this;
        delete e;
    });
    runLoop->addEvent(event);
    postedDestroy = true;
}

